// FILE: Chip8Disassembler.cpp by Richard Hughes 30/04/2012
//
// DESC: Defines a Chip 8 disassembler

// INCLUDES
#include <sstream>
#include <string>
#include "Chip8Disassembler.h"
#include "Chip8ROM.h"
#include "Chip8RAM.h"

// NAMESPACES
using namespace std;

namespace Emulator
{
namespace Chip8
{

/*************************************************************/
//
// CLASS: Chip8Disassembler
//
// DESC: The Chip 8 Disassembler
//
/*************************************************************/

// performs the disassemble from the passed ROM
// takes the ROM with the code to disassemble
// returns '\n' deliminated string with the disassembled code
string Chip8Disassembler::Disassemble(IROM* rom)
{
	int size = rom->Size();
	const uint8_t* data = rom->GetRawData();

	return this->Disassemble(data, size);
} // end Disassemble

// performs the disassemble
// takes the code to disassemble
// returns '\n' deliminated string with the disassembled code
string Chip8Disassembler::Disassemble(const uint8_t* data, int size)
{
	string disassembledCode = "";

	for(int i = 0; i < size; i += 2)
	{
		string currentOp = "";

		uint8_t left = data[i];
		uint8_t right = data[i + 1];

		uint16_t op = (left << 8) | right;

		uint8_t code = op >> 12;

		switch(code)
		{
		case 0x0:
			{
				currentOp = this->Do0x0(op);
				break;
			}
		case 0x1:
			{
				currentOp = this->Do0x1(op);
				break;
			}
		case 0x2:
			{
				currentOp = this->Do0x2(op);
				break;
			}
		case 0x3:
			{
				currentOp = this->Do0x3(op);
				break;
			}
		case 0x4:
			{
				currentOp = this->Do0x4(op);
				break;
			}
		case 0x5:
			{
				currentOp = this->Do0x5(op);
				break;
			}
		case 0x6:
			{
				currentOp = this->Do0x6(op);
				break;
			}
		case 0x7:
			{
				currentOp = this->Do0x7(op);
				break;
			}
		case 0x8:
			{
				currentOp = this->Do0x8(op);
				break;
			}
		case 0x9:
			{
				currentOp = this->Do0x9(op);
				break;
			}
		case 0xA:
			{
				currentOp = this->Do0xA(op);
				break;
			}
		case 0xB:
			{
				currentOp = this->Do0xB(op);
				break;
			}
		case 0xC:
			{
				currentOp = this->Do0xC(op);
				break;
			}
		case 0xD:
			{
				currentOp = this->Do0xD(op);
				break;
			}
		case 0xE:
			{
				currentOp = this->Do0xE(op);
				break;
			}
		case 0xF:
			{
				currentOp = this->Do0xF(op);
				break;
			}
		default:
			{
				ostringstream oss;
				oss << "Unknown Opcode: " << std::hex << op;
				currentOp = oss.str();
			}
		}; // end switch

		ostringstream oss;
		oss << std::hex << BASE_WRITABLE + i << ": " << currentOp << endl;
		disassembledCode += oss.str();

	} // end for

	return disassembledCode;
} // Disassemble

// these handle the respective opcodes
// they take the opcode to handle
// they return nothing
string Chip8Disassembler::Do0x0(uint16_t op)
{
	switch(op)
	{
	case 0x00E0: // CLS
		{
			return "CLS";
			break;
		}
	case 0x00EE: // RET
		{
			return "RET";
			break;
		}
	case 0x00FB:
		{
			return "SCR (SCHIP8)";
			break;
		}
	case 0x00FC:
		{
			return "SCL (SCHIP8)";
			break;
		}
	case 0x00FD:
		{
			return "EXIT (SCHIP8)";
			break;
		}
	case 0x00FE:
		{
			return "LOW (SCHIP8)";
			break;
		}
	case 0x00FF:
		{
			return "HIGH (SCHIP8)";
			break;
		}
	}

	// possibly some others...
	uint8_t code = (op >> 4) & 0x00F;

	switch(code)
	{
	case 0xC:
		{
			uint8_t n = op & 0x000F;

			ostringstream oss;
			oss << "SCD " << std::hex << (int)n << " (SCHIP8)";

			return oss.str();
			break;
		}
	}

	ostringstream oss;
	oss << "Unknown Opcode internal 0x0: " << std::hex << op;
	return oss.str();
} // end Do0x0

string Chip8Disassembler::Do0x1(uint16_t op)
{
	// JP addr
	uint16_t addr = op & 0x0FFF;

	ostringstream oss;
	oss << "JP " << std::hex << addr;

	return oss.str();
} // end Do0x1

string Chip8Disassembler::Do0x2(uint16_t op)
{
	// CALL addr
	uint16_t addr = op & 0x0FFF;

	ostringstream oss;
	oss << "CALL " << std::hex << addr;

	return oss.str();
} // end Do0x2

string Chip8Disassembler::Do0x3(uint16_t op)
{
	// SE Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	ostringstream oss;
	oss << "SE V" << std::hex << (int)x << ", " << std::hex << (int)kk;

	return oss.str();
} // end Do0x3

string Chip8Disassembler::Do0x4(uint16_t op)
{
	// SNE Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	ostringstream oss;
	oss << "SNE V" << std::hex << (int)x << ", " << std::hex << (int)kk;

	return oss.str();
} // end Do0x4

string Chip8Disassembler::Do0x5(uint16_t op)
{
	// SE Vx, Vy
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t y = op & 0x000F;

	ostringstream oss;
	oss << "SE V" << std::hex << (int)x << ", V" << std::hex << (int)y;

	return oss.str();
} // end Do0x5

string Chip8Disassembler::Do0x6(uint16_t op)
{
	// LD Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	ostringstream oss;
	oss << "LD V" << std::hex << (int)x << ", " << std::hex << (int)kk;

	return oss.str();
} // end Do0x06

string Chip8Disassembler::Do0x7(uint16_t op)
{
	// ADD Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	ostringstream oss;
	oss << "ADD V" << std::hex << (int)x << ", " << std::hex << (int)kk;

	return oss.str();
} // end Do0x7

string Chip8Disassembler::Do0x8(uint16_t op)
{
	uint8_t instruction = op & 0x000F;

	switch(instruction)
	{
	case 0: // LD Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "LD V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 1: // OR Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "OR V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 2: // AND Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "AND V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 3: // XOR Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "XOR V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 4: // ADD Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "ADD V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 5: // SUB Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "SUB V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 6: // SHR Vx {, Vy}
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "SHR V" << std::hex << (int)x << "{, Vy}";

			return oss.str();
			break;
		}
	case 7: // SUBN Vx, Vy
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint8_t y = (op >> 4) & 0x00F;

			ostringstream oss;
			oss << "SUBN V" << std::hex << (int)x << ", V" << std::hex << (int)y;

			return oss.str();
			break;
		}
	case 0xE: // SHL Vx {, Vy}
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "SHL V" << std::hex << (int)x << "{, Vy}";

			return oss.str();
			break;
		}
	}

	ostringstream oss;
	oss << "Unknown Opcode internal 0x8: " << std::hex << op;
	return oss.str();
} // end Do0x8

string Chip8Disassembler::Do0x9(uint16_t op)
{
	// SNE Vx, Vy
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t y = (op >> 4) & 0x00F;

	ostringstream oss;
	oss << "SNE V" << std::hex << (int)x << ", V" << std::hex << (int)y;

	return oss.str();
} // end Do0x9

string Chip8Disassembler::Do0xA(uint16_t op)
{
	// LD I, addr
	ostringstream oss;
	oss << "LD I, " << std::hex << (op & 0x0FFF);

	return oss.str();
} // end Do0xA

string Chip8Disassembler::Do0xB(uint16_t op)
{
	// JP V0, addr
	ostringstream oss;
	oss << "JP V0, " << std::hex << (op & 0x0FFF);

	return oss.str();
} // end Do0xB

string Chip8Disassembler::Do0xC(uint16_t op)
{
	// RND Vx, kk
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t kk = op & 0x00FF;

	ostringstream oss;
	oss << "RND V" << std::hex << (int)x << ", " << std::hex << (int)kk;

	return oss.str();
} // end Do0xC

string Chip8Disassembler::Do0xD(uint16_t op)
{
	// DRW Vx, Vy, nibble
	uint8_t x = (op >> 8) & 0x0F;
	uint8_t y = (op >> 4) & 0x00F;
	uint8_t n = op & 0x000F;

	ostringstream oss;

	if(n == 0)
	{
		oss << "DRW V" << std::hex << (int)x << ", V" << std::hex << (int)y << ", " << std::hex << (int)n << " (SCHIP8) - 16 bit wide sprite";
	}
	else
	{
		oss << "DRW V" << std::hex << (int)x << ", V" << std::hex << (int)y << ", " << std::hex << (int)n;
	}

	return oss.str();
} // end Do0xD

string Chip8Disassembler::Do0xE(uint16_t op)
{
	uint8_t instruction = op & 0x00FF;

	switch(instruction)
	{
	case 0x9E: // SKP Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "SKP V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0xA1: // SKNP Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "SKNP V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	}

	ostringstream oss;
	oss << "Unknown Opcode internal 0xE: " << std::hex << op;
	return oss.str();
} // end Do0xE

string Chip8Disassembler::Do0xF(uint16_t op)
{
	uint8_t instruction = op & 0x00FF;

	switch(instruction)
	{
	case 0x07: // LD Vx, DT
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD V" << std::hex << (int)x << ", DT";

			return oss.str();
			break;
		}
	case 0x0A: // LD Vx, K
		{
			uint8_t x = (op >> 8) & 0x0F;
			uint16_t K = op & 0x00FF; // this could be wrong, I havn't read the docs about keys yet

			ostringstream oss;
			oss << "LD V" << std::hex << (int)x << ", " << std::hex << K;

			return oss.str();
			break;
		}
	case 0x15: // LD DT, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD DT, V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0x18: // LD ST, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD ST, V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0x1E: // ADD I, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "ADD I, V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0x29: // LD F, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD F, V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0x30: // LD HF, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD HF, V" << std::hex << (int)x << " (SCHIP8)";

			return oss.str();
			break;
		}
	case 0x33: // LD B, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD B, V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0x55: // LD [I], Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD [I], V" << std::hex << (int)x;

			return oss.str();
			break;
		}
	case 0x65: // LD Vx, [I]
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD V" << std::hex << (int)x << ", [I]";

			return oss.str();
			break;
		}
	case 0x75: // LD R, Vx
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD R, V" << std::hex << (int)x << " (SCHIP8)";

			return oss.str();
			break;
		}
	case 0x85: // LD Vx, R
		{
			uint8_t x = (op >> 8) & 0x0F;

			ostringstream oss;
			oss << "LD  V" << std::hex << (int)x << ", R (SCHIP8)";

			return oss.str();
			break;
		}
	}

	ostringstream oss;
	oss << "Unknown Opcode internal 0xF: " << std::hex << op;
	return oss.str();
} // end Do0xF

} // end namespace Chip8
} // end namespace Emulator

// EOF: Chip8Disassembler.cpp